package 多线程;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;

/**
 * @Author 廖述京
 * @Date 2017/8/31
 */
/*
  Java多线程实现方式主要有四种：
  1、无返回值的：继承Thread类、实现Runnable接口、
  2、有返回值的：实现Callable接口，通过FutureTask包装器来创建Thread线程
             使用ExecutorService(接口)、Callable(接口)、Future(接口)实现有返回结果的多线程。
   有返回值的Thread类:
   共同点：都只实现Callable接口
   不同点：
   (1)前者创建执行线程方式：new Thread( new FutureTask(new 实现Callable的类()) ).start();
      获得线程返回值：futureTask.get()
   (2)后者创建执行线程的方式：Future f = new ExecutorService的实现类().submit(new 实现Callable的类());
      获得线程返回值：future.get()
 */
public class java多线程实现的四种方式 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //1、启动线程1
        MyThread1 myThread1 = new MyThread1();
        myThread1.start();
        //2、启动线程2
        Thread myThread2 = new Thread(new MyThread2());
        myThread2.start();
        //3、启动线程3 --有返回值的
        MyThread3 myThread3 = new MyThread3();
        FutureTask futureTask = new FutureTask<>(myThread3); //FutureTask<Integer>是一个包装器，
        // 它通过接受Callable<Integer>来创建，它同时实现了Future和Runnable接口
        Thread threadCallable = new Thread(futureTask); //创建线程
        threadCallable.start(); //启动线程
        System.out.println(futureTask.get());  //警告：必须在线程启动后，才能获得线程的返回值！否则将一直阻塞！

        //4、启动线程 --有返回值的
        startThreadForExecutor();    //启动线程代码
    }

    //专门用于测试有返回值的线程
    private static void startThreadForExecutor() throws InterruptedException, ExecutionException {
       System.out.println("----程序开始运行----");

        Date date1 = new Date();

        int taskSize = 5;
        // 创建一个线程池
        ExecutorService pool = Executors.newFixedThreadPool(taskSize); //创建固定数目线程的线程池。
                 //Executors类，提供了一系列工厂方法用于创建线程池，返回的线程池都实现了ExecutorService接口。
        // 创建多个有返回值的任务
        List<Future> list = new ArrayList<Future>();
        for (int i = 0; i < taskSize; i++) {
            Callable c = new MyCallable(i + " ");
            // 执行任务并获取Future对象
            Future f = pool.submit(c);  //ExecutoreService提供了submit()方法，传递一个Callable，或Runnable，返回Future。
                     // 如果Executor后台线程池还没有完成Callable的计算，这调用返回Future对象的get()方法，会阻塞直到计算完成。
//            System.out.println(">>>" + f.get().toString()); //放在此处，会阻塞其他线程的执行
            list.add(f);
        }
        // 关闭线程池
        pool.shutdown();

        // 获取所有并发任务的运行结果(等所有的任务都执行后，再来获得线程的返回值，会并发执行)
        for (Future f : list) {
            // 从Future对象上获取任务的返回值，并输出到控制台
            System.out.println(">>>" + f.get().toString());
        }

        Date date2 = new Date();
        System.out.println("----程序结束运行----，程序运行时间【"
                + (date2.getTime() - date1.getTime()) + "毫秒】");
    }

    //实现多线程方式1：继承Thread类
    //Thread类本质上是实现了Runnable接口的一个实例，代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法。
    //start() -> start0()(这是一个native方法,后期的run方法将有JVM来负责调用) -> run()
    static class MyThread1 extends Thread {
        @Override
        public void run() {
            System.out.println("1、running...");
        }
    }

    //实现多线程方式2：实现Runnable接口
    //如果自己的类已经extends另一个类，就无法直接extends Thread，此时，可以实现一个Runnable接口
    static class MyThread2 implements Runnable {
        @Override
        public void run() {
            System.out.println("2、running...");
        }
    }

    //实现多线程方式3：实现Callable接口，通过FutureTask包装器来创建Thread线程
    //Callable接口也只有一个方法,如下：
    //  public interface Callable<V>   {
    //     V call（） throws Exception;
    //  }
    static class MyThread3 implements Callable {
        @Override
        public Object call() throws Exception {
            System.out.println("3、calling...");
            return "线程3返回值";
        }
    }

    //实现多线程方式4：使用ExecutorService、Callable、Future实现有返回结果的线程
    /*
    ExecutorService、Callable、Future三个接口实际上都是属于Executor框架。返回结果的线程是在JDK1.5中引入的新特征，
       有了这种特征就不需要再为了得到返回值而大费周折了。而且自己实现了也可能漏洞百出。可返回值的任务必须实现Callable接口。
    类似的，无返回值的任务必须实现Runnable接口。
          执行Callable任务后，可以获取一个Future的对象，在该对象上调用get就可以获取到Callable任务返回的Object了。
    注意：get方法是阻塞的，即：线程无返回结果，get方法会一直等待。再结合线程池接口ExecutorService就可以
         实现传说中有返回结果的多线程了。
     */
    static class MyCallable implements Callable<Object> { //类的定义和方式3一样
        private String taskNum;

        MyCallable(String taskNum) {
            this.taskNum = taskNum;
        }

        public Object call() throws Exception {
            System.out.println(">>>" + taskNum + "任务启动");
            Date dateTmp1 = new Date();
            Thread.sleep(1000);
            Date dateTmp2 = new Date();
            long time = dateTmp2.getTime() - dateTmp1.getTime();
            System.out.println(">>>" + taskNum + "任务终止");
            return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】";
        }
    }
}

